#include <iostream>
#include <string.h>
#include <pthread.h>
#include <sys/time.h>
#include "client.h"

// Forward declares
//
void usage();
void runcmd(const short threads, const char* hostname, 
            const short port, const char* cmd, bool isMuttering);
void* runcmd_func(void* args);

// Package of arguments into threading function
//
struct runcmd_args
{
    const char* hostname;
    short port;
    const char* cmd;
    bool mutter;
    
    // Simple init via constructor
    //
    runcmd_args() :
        hostname(NULL),
        port(kDefaultPort),
        cmd(NULL),
        mutter(true)
    {
    }
};

// Used to return time info to thread parent
//
struct TimeInfo
{
    timeval startTime, endTime;
};


// Run the command on the server and display output
//
void* runcmd_func(void* vpargs)
{
    runcmd_args* args = (runcmd_args*) vpargs;
    
    TimeInfo* pTimeInfo = new TimeInfo();
    
    Client client(args->hostname, args->port);

	// Get starting time
    //
    gettimeofday(&pTimeInfo->startTime, NULL); 

    if (client.open())
    {
        client.send(args->cmd);
        client.receive(args->mutter);
    }
    else 
    {
        std::cout << "Error: Unable to execute \"" << args->cmd << "\"\n";
    }
    
    // Get time now that all threads have joined back together
    //
    gettimeofday(&pTimeInfo->endTime, NULL); 
    
    pthread_exit(pTimeInfo);
}

// Run the command on the server and display output
//
void runcmd(const short threads, const char* hostname, 
            const short port, const char* cmd, bool isMuttering)
{
    // Time tracking variables
    //
    long timeLapse_sec = 0L;
    long timeLapse_usec = 0L;
    
    // Thread id array
    //
    pthread_t thread[threads];
    
    // Arguments for thread function
    //
    runcmd_args args;
    args.hostname = hostname;
    args.port = port;
    args.cmd = cmd;
    args.mutter = isMuttering;
    
    
    // Run the command in each thread
    //
    for (short i = 0; i < threads; ++i)
    {
        int ret = pthread_create(&(thread[i]), NULL, runcmd_func, (void*) &args);
        if (ret != 0)
        {
            std::cout << "Error: Unable to create thread " << i << ".\n";
        }
    }
    
    // Wait on all the threads to complete
    //
    for (short i = 0; i < threads; ++i)
    {
        TimeInfo* pTime = NULL;

        int ret = pthread_join(thread[i], (void**) &pTime);
        if (ret != 0)
        {
            std::cout << "Error: Unable to wait on thread " << i << ".\n";
        }
        else 
        {
            // Calculate time lapse accounting for all cases
            //
            if (pTime->endTime.tv_sec > pTime->startTime.tv_sec)
            {
                timeLapse_sec += pTime->endTime.tv_sec - pTime->startTime.tv_sec;
            }

            
            if (pTime->endTime.tv_usec > pTime->startTime.tv_usec)
            {
                timeLapse_usec += pTime->endTime.tv_usec - pTime->startTime.tv_usec;
            }
            else if (pTime->endTime.tv_usec < pTime->startTime.tv_usec)
            {
                timeLapse_usec += pTime->endTime.tv_usec + (1000000 - pTime->startTime.tv_usec);
                timeLapse_sec--;
            }
        }
        
        // Delete time info
        //
        delete pTime;
    }
    
    timeLapse_sec /= threads;
    timeLapse_usec  /= threads;
    
    
    // Display start time, end time, and time lapse
    //
//    std::cout << "\nStart Time: " << startTime.tv_sec << " seconds and " << (startTime.tv_usec / 1000) << " milliseconds\n";
//    std::cout << "End Time: " << endTime.tv_sec << " seconds and " << (endTime.tv_usec / 1000) << " milliseconds\n";
    std::cout << "\nTime Lapse: " << timeLapse_sec << " seconds and " << (timeLapse_usec /1000) << " milliseconds\n";
    std::cout << '\n';
}

// Display program usage
//
void usage()
{
    std::cout << "client [options]\n";
    std::cout << "Options:\n";
    std::cout << "  --help\t\tDisplay usage information\n";
    std::cout << "  -h [hostname]\t\tServer name; default local machine\n";
    std::cout << "  -p [port]\t\tPort number; default 9000\n";
    std::cout << "  -t [threads]\t\tNumber of simultanous clients; default 1\n";
    std::cout << "  -m \t\t\tDisables muttering of incoming data\n";
    
    exit(0);
}

// Client entry point
//
int main (int argc, char * const argv[]) 
{
    char  hostname[MAXHOSTNAMELEN];
    short port = kDefaultPort;
    short threads = 1;
    bool isMuttering = true;
 
    // Assign default host name to the current machine
    //
    memset(hostname, '\0', sizeof(hostname));
    if (gethostname(hostname, MAXHOSTNAMELEN - 1) != 0)
    {
        std::cout << "Error: Unable to get host name. Exiting program.\n";
        exit(1);
    }
    
    // If no parameters are passed in then display usage and quit
    //
    if (argc  < 2)
    {
        usage();
    }
           
    // Process arguments to set the host name and port
    //
    for (int i = 1; i < argc; ++i)
    {
        if (strcmp("--help", argv[i]) == 0)
        {
            usage();
        }
        else if (strcmp("-h", argv[i]) == 0 && i + 1 < argc)
        {
            ++i;    // extra step
            strcpy(hostname, argv[i]);
        }
        else if (strcmp("-p", argv[i]) == 0 && i + 1 < argc)
        {
            ++i;    // extra step
            port = (short) atoi(argv[i]);
        }
        else if (strcmp("-t", argv[i]) == 0 && i + 1 < argc)
        {
            ++i;    // extra step
            threads = (short) atoi(argv[i]);
        }
        else if (strcmp("-m", argv[i]) == 0)
        {
            isMuttering = false;
        }
        else 
        {
            // Invalid parameters
            //
            usage();
        }
    }
    
    // Loop menu until user selects to quit
    //
    bool isDone = false;
    while (!isDone)
    {
        char input = '\0';
        
        std::cout << "Hostname:\t" << hostname << '\n';
        std::cout << "Port:\t\t" << port << '\n';
        std::cout << "Threads:\t" << threads << "\n\n";
        
        std::cout << "1. Host current Date and Time\n"
                     "2. Host uptime\n"
                     "3. Host memory use\n"
                     "4. Host Netstat\n"
                     "5. Host current users\n"
                     "6. Host running processes\n"
                     "7. Quit\n\n";
        
        std::cout << "Enter the corresponding number and press <ENTER>: ";   
        std::cin >> input;
        
        std::cout << "\n";
        switch (input)
        {
        case '1':
            runcmd(threads, hostname, port, "date", isMuttering);
            break;
        case '2':
            runcmd(threads, hostname, port, "uptime", isMuttering);
            break;
        case '3':
            runcmd(threads, hostname, port, "free", isMuttering);
            break;
        case '4':
            runcmd(threads, hostname, port, "netstat", isMuttering);
            break;
        case '5':
            runcmd(threads, hostname, port, "who", isMuttering);
            break;
        case '6':
            runcmd(threads, hostname, port, "ps -e", isMuttering);
            break;
        case '7':
            isDone = true;
            break;
        default:
            std::cout << "Error: Invalid command, try again.\n\n";
            break;
        }
    }    
    
    return 0;
}
